/**
 * 
 */
package junior.util.aop.annotation.aspectj;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import junior.util.aop.annotation.AnnotationContext;
import junior.util.aop.annotation.AnnotationInvoker;
import junior.util.reflect.ReflectUtils;

public class AnnotationInterceptor implements MethodInterceptor {
	private AnnotationInvoker invoker;

	public void setInvoker(AnnotationInvoker invoker) {
		this.invoker = invoker;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
	    boolean supports = false;

		Class<? extends Annotation>[] supptAnnoClasses = invoker.getSupportAnnotations();
	    Set<Annotation> annoSet = new HashSet<Annotation>(1);
    	for (Class<? extends Annotation> supptAnnoClass : supptAnnoClasses) {
    		Annotation a = getAnnotation(mi, supptAnnoClass);
    		if (a != null) {
	    		if (! supports) supports = true;
	    		annoSet.add(a);
    		}
    	}
    	
    	if (! supports) return mi.proceed();
		
        Method m = mi.getMethod();
        
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(m);
        if (paramNames == null) {
            m = mi.getThis().getClass().getMethod(m.getName(), m.getParameterTypes());
            paramNames = u.getParameterNames(m);
        }
        
        Object[] args = mi.getArguments();
        
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        if (paramNames != null)
        for (int i=0; i<paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        
        List<AnnotationContext> ctxList = new LinkedList<AnnotationContext>();
    	
    	for (Annotation a : annoSet) {
    		if (a == null) continue;
    		
    		AnnotationContext ctx = new AnnotationContext(a);
    		
    		Method[] methods = getAnnotationMethods(a);
    		
    		for (Method _method : methods) {
    			String _name = _method.getName();
    			Object v = _method.invoke(a);
    			
    			if (_method.getReturnType().equals(String.class) && invoker.isSupportsSpel(a, _name)) {
    				String value = (String) _method.invoke(a);
    				if (StringUtils.hasText(value)) {
	    				v = parser.parseExpression(value).getValue(context, Object.class);
	    				ctx.set(_method.getName(), v);
	    				continue;
    				}
    			}
        		
    			ctx.set(_method.getName(), v);
    		}
    		
    		ctxList.add(ctx);
    	}
    	
    	return invoker.invoke(mi, ctxList);
	}
	
	private Method[] getAnnotationMethods(Annotation a) {
    	java.lang.reflect.Method[] methods = a.getClass().getDeclaredMethods();
    	
    	List<Method> ms = new LinkedList<Method>();
    	for (java.lang.reflect.Method method : methods) {
    		if (method.getParameterTypes().length == 0 && ! ReflectUtils.isInheritFromObject(method)) {
        		ms.add(method);
    		}
    	}
    	return ms.toArray(new Method[ms.size()]);
	}

    private Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        Method m = mi.getMethod();

        Annotation a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;

        Class<?> targetClass = mi.getThis().getClass();
        m = ClassUtils.getMostSpecificMethod(m, targetClass);
        a = AnnotationUtils.findAnnotation(m, clazz);
        if (a != null) return a;
        
        return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz);
    }
    
}
